/*
 * Copyright (C) 2001-2011 Jacek Sieka, arnetheduck on gmail point com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifndef DCPLUSPLUS_DCPP_QUEUE_ITEM_H
#define DCPLUSPLUS_DCPP_QUEUE_ITEM_H

#include "User.h"
#include "FastAlloc.h"
#include "MerkleTree.h"
#include "Flags.h"
#include "forward.h"
#include "Segment.h"
#include "HintedUser.h"

const static string TEMP_EXTENSION = ".dctmp";

class QueueManager;
class Download;

class QueueItem : public Flags, public FastAlloc<QueueItem>, public intrusive_ptr_base<QueueItem>
{
	public:
	typedef unordered_map<string*, QueueItemPtr, noCaseStringHash, noCaseStringEq> StringMap;
	
		
		enum Priority
		{
			DEFAULT = -1,
			PAUSED = 0,
			LOWEST,
			LOW,
			NORMAL,
			HIGH,
			HIGHEST,
			LAST
		};
		
		enum FileFlags
		{
			/** Normal download, no flags set */
			FLAG_NORMAL             = 0x00,
			/** This is a user file listing download */
			FLAG_USER_LIST          = 0x01,
			/** The file list is downloaded to use for directory download (used with USER_LIST) */
			FLAG_DIRECTORY_DOWNLOAD = 0x02,
			/** The file is downloaded to be viewed in the gui */
			FLAG_CLIENT_VIEW        = 0x04, //-V112
			/** Flag to indicate that file should be viewed as a text file */
			FLAG_TEXT               = 0x08,
			/** Match the queue against this list */
			FLAG_MATCH_QUEUE        = 0x10,
			/** The file list downloaded was actually an .xml.bz2 list */
			FLAG_XML_BZLIST         = 0x20, //-V112
			/** Only download a part of the file list */
			FLAG_PARTIAL_LIST       = 0x40,
			/** Test user's file list for fake share */
			FLAG_USER_CHECK         = 0x80,
			/** Autodrop slow source is enabled for this file */
			FLAG_AUTODROP           = 0x100,
			/** [+] SSA - check User IP */
			FLAG_USER_GET_IP        = 0x200,
			/** [+] SSA - dclst support */
			FLAG_DCLST_LIST			= 0x400,
		};
		
		/**
		 * Source parts info
		 * Meaningful only when Source::FLAG_PARTIAL is set
		 */
		class PartialSource : public FastAlloc<PartialSource>, public intrusive_ptr_base<PartialSource>
		{
			public:
				PartialSource(const string& aMyNick, const string& aHubIpPort, const string& aIp, uint16_t udp) :
					myNick(aMyNick), hubIpPort(aHubIpPort), ip(aIp), udpPort(udp), nextQueryTime(0), pendingQueryCount(0) { }
					
				~PartialSource() { }
				
				typedef boost::intrusive_ptr<PartialSource> Ptr;
				
				GETSET(PartsInfo, partialInfo, PartialInfo);
				GETSET(string, myNick, MyNick);         // for NMDC support only
				GETSET(string, hubIpPort, HubIpPort);
				GETSET(string, ip, Ip);
				GETSET(uint64_t, nextQueryTime, NextQueryTime);
				GETSET(uint16_t, udpPort, UdpPort);
				GETSET(uint8_t, pendingQueryCount, PendingQueryCount);
		};
		
		class Source : public Flags
		{
			public:
				enum
				{
					FLAG_NONE               = 0x00,
					FLAG_FILE_NOT_AVAILABLE = 0x01,
					FLAG_PASSIVE            = 0x02,
					FLAG_REMOVED            = 0x04, //-V112
					FLAG_NO_TTHF            = 0x08,
					FLAG_BAD_TREE           = 0x10,
					FLAG_SLOW_SOURCE        = 0x20, //-V112
					FLAG_NO_TREE            = 0x40,
					FLAG_NO_NEED_PARTS      = 0x80,
					FLAG_PARTIAL            = 0x100,
					FLAG_TTH_INCONSISTENCY  = 0x200,
					FLAG_UNTRUSTED          = 0x400,
					FLAG_MASK               = FLAG_FILE_NOT_AVAILABLE
					| FLAG_PASSIVE | FLAG_REMOVED | FLAG_BAD_TREE | FLAG_SLOW_SOURCE
					| FLAG_NO_TREE | FLAG_TTH_INCONSISTENCY | FLAG_UNTRUSTED
				};
				
				Source(const HintedUser& aUser) : user(aUser), partialSource(NULL) { }
				Source(const Source& aSource) : Flags(aSource), user(aSource.user), partialSource(aSource.partialSource) { }
				
				bool operator==(const UserPtr& aUser) const
				{
					return user == aUser;
				}
				PartialSource::Ptr& getPartialSource()
				{
					return partialSource;
				}
				
				GETSET(HintedUser, user, User);
				GETSET(PartialSource::Ptr, partialSource, PartialSource);
		};
		
		typedef vector<Source> SourceList;
		typedef SourceList::iterator SourceIter;
		typedef SourceList::const_iterator SourceConstIter;
		
		typedef set<Segment> SegmentSet;
		typedef SegmentSet::const_iterator SegmentConstIter;
		
		QueueItem(const string& aTarget, int64_t aSize, Priority aPriority, Flags::MaskType aFlag,
		          time_t aAdded, const TTHValue& tth);
		          
		QueueItem(const QueueItem& rhs) :
			Flags(rhs), done(rhs.done), downloads(rhs.downloads), target(rhs.target),
			size(rhs.size), priority(rhs.priority), added(rhs.added), tthRoot(rhs.tthRoot),
			autoPriority(rhs.autoPriority), maxSegments(rhs.maxSegments), fileBegin(rhs.fileBegin),
			sources(rhs.sources), badSources(rhs.badSources), tempTarget(rhs.tempTarget), nextPublishingTime(rhs.nextPublishingTime),
			viewQueueID(rhs.viewQueueID),
			m_dirty(rhs.m_dirty)
		{
			inc();
		}
		
		~QueueItem() { }
		
		size_t countOnlineUsers() const;
		void getOnlineUsers(HintedUserList& l) const;
		
		SourceList& getSources()
		{
			return sources;
		}
		const SourceList& getSources() const
		{
			return sources;
		}
		SourceList& getBadSources()
		{
			return badSources;
		}
		const SourceList& getBadSources() const
		{
			return badSources;
		}
		
		string getTargetFileName() const
		{
			return Util::getFileName(getTarget());
		}
		
		SourceIter getSource(const UserPtr& aUser)
		{
			return find(sources.begin(), sources.end(), aUser);
		}
		SourceIter getBadSource(const UserPtr& aUser)
		{
			return find(badSources.begin(), badSources.end(), aUser);
		}
		SourceConstIter getSource(const UserPtr& aUser) const
		{
			return find(sources.begin(), sources.end(), aUser);
		}
		SourceConstIter getBadSource(const UserPtr& aUser) const
		{
			return find(badSources.begin(), badSources.end(), aUser);
		}
		
		bool isSource(const UserPtr& aUser) const
		{
			return getSource(aUser) != sources.end();
		}
		bool isBadSource(const UserPtr& aUser) const
		{
			return getBadSource(aUser) != badSources.end();
		}
		bool isBadSourceExcept(const UserPtr& aUser, Flags::MaskType exceptions) const
		{
			SourceConstIter i = getBadSource(aUser);
			if (i != badSources.end())
				return i->isAnySet((Flags::MaskType)(exceptions ^ Source::FLAG_MASK));
			return false;
		}
		
		void getChunksVisualisation(int type, vector<Segment>& p_segments) const;
		
		bool isChunkDownloaded(int64_t startPos, int64_t& len) const
		{
			if (len <= 0) return false;
			
			for (SegmentSet::const_iterator i = done.begin(); i != done.end(); ++i)
			{
				int64_t first  = (*i).getStart();
				int64_t second = (*i).getEnd();
				
				if (first <= startPos && startPos < second)
				{
					len = min(len, second - startPos);
					return true;
				}
			}
			
			return false;
		}
		
		/**
		 * Is specified parts needed by this download?
		 */
		bool isNeededPart(const PartsInfo& partsInfo, int64_t blockSize);
		
		/**
		 * Get shared parts info, max 255 parts range pairs
		 */
		void getPartialInfo(PartsInfo& partialInfo, int64_t blockSize) const;
		
		uint64_t getDownloadedBytes() const;
		double getDownloadedFraction() const
		{
			return static_cast<double>(getDownloadedBytes()) / getSize();
		}
		
		bool isDirty() const
		{
			return m_dirty;
		}
		void setDirty(bool p_dirty = true)
		{
			m_dirty = p_dirty;
		}
		
		DownloadList& getDownloads()
		{
			return downloads;
		}
		
		/** Next segment that is not done and not being downloaded, zero-sized segment returned if there is none is found */
		Segment getNextSegment(int64_t blockSize, int64_t wantedSize, int64_t lastSpeed, const PartialSource::Ptr &partialSource) const;
		
		void addSegment(const Segment& segment);
		void resetDownloaded()
		{
			done.clear();
		}
		
		bool isFinished() const
		{
			return done.size() == 1 && *done.begin() == Segment(0, getSize());
		}
		
		bool isRunning() const
		{
			return !isWaiting();
		}
		bool isWaiting() const
		{
			return downloads.empty();
		}
		
		string getListName() const
		{
			dcassert(isSet(QueueItem::FLAG_USER_LIST) || isSet (QueueItem::FLAG_DCLST_LIST));
			if (isSet(QueueItem::FLAG_XML_BZLIST))
			{
				return getTarget() + ".xml.bz2";
			}
			else if (isSet(QueueItem::FLAG_DCLST_LIST))
			{
				return getTarget();
			}else
			{
				return getTarget() + ".xml";
			}
		}
		
		const string& getTempTarget();
		void setTempTarget(const string& aTempTarget)
		{
			m_dirty = true;
			tempTarget = aTempTarget;
		}
		
#define GETSET_DIRTY(type, name, name2) \
private: type name; \
public: TypeTraits<type>::ParameterType get##name2() const { return name; } \
	void set##name2(TypeTraits<type>::ParameterType a##name2) {m_dirty = true; name = a##name2; }
		
		bool m_dirty;
		GETSET_DIRTY(TTHValue, tthRoot, TTH);
		GETSET_DIRTY(SegmentSet, done, Done);
		GETSET_DIRTY(DownloadList, downloads, Downloads);
		GETSET_DIRTY(string, target, Target);
		GETSET_DIRTY(uint64_t, fileBegin, FileBegin);
		GETSET_DIRTY(uint64_t, nextPublishingTime, NextPublishingTime);
		GETSET_DIRTY(int64_t, size, Size);
		GETSET_DIRTY(time_t, added, Added);
		GETSET_DIRTY(Priority, priority, Priority);
		GETSET_DIRTY(uint8_t, maxSegments, MaxSegments);
		GETSET_DIRTY(bool, autoPriority, AutoPriority);
		GETSET_DIRTY(int64_t, viewQueueID, ViewQueueID);
		
		QueueItem::Priority calculateAutoPriority() const
		{
			if (autoPriority)
			{
				QueueItem::Priority p;
				int percent = static_cast<int>(getDownloadedBytes() * 10.0 / size);
				switch (percent)
				{
					case 0:
					case 1:
					case 2:
						p = QueueItem::LOW;
						break;
					case 3:
					case 4:
					case 5:
					default:
						p = QueueItem::NORMAL;
						break;
					case 6:
					case 7:
					case 8:
						p = QueueItem::HIGH;
						break;
					case 9:
					case 10:
						p = QueueItem::HIGHEST;
						break;
				}
				return p;
			}
			return priority;
		}
		
		uint64_t getAverageSpeed() const;
		
	private:
		QueueItem& operator=(const QueueItem&);
		
		friend class QueueManager;
		SourceList sources;
		SourceList badSources;
		string tempTarget;
		
		void addSource(const HintedUser& aUser);
		void removeSource(const UserPtr& aUser, Flags::MaskType reason);
};

#endif // !defined(QUEUE_ITEM_H)

/**
* @file
* $Id: QueueItem.h 568 2011-07-24 18:28:43Z bigmuscle $
*/
